SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
yesterdaylast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
27.3 KB · 489 lines tsx
Raw Blame History
1import type { CountryDetail, FacilityType } from "@dci/core";2import type { Metadata } from "next";3import Link from "next/link";4import { notFound } from "next/navigation";5import { Breadcrumbs } from "@/components/entity/breadcrumbs";6import { EntityMap } from "@/components/entity/entity-map";7import { JsonLd } from "@/components/entity/json-ld";8import { fPageHref, PagedFacilities, parseFPage } from "@/components/entity/paged-facilities";9import { SectionNav } from "@/components/entity/section-nav";10import { SourcesFooter } from "@/components/entity/sources-footer";11import { EventList } from "@/components/event-list";12import { ClaimsTable } from "@/components/evidence";13import { FacilityTable } from "@/components/facility-table";14import { EnergyContextPanel } from "@/components/intelligence/country/energy";15import { CoverageBar, CoverageStrip, GridConstraintList, HonestEmpty, PillLink, PipelineBars, PublishedOnlyNote, ShareList } from "@/components/intelligence/shared";16import { WatchButton } from "@/components/intelligence/watch-button";17import { ProjectList } from "@/components/project-list";18import { Badge, EstimateMark } from "@/components/ui/badge";19import { Page, Section } from "@/components/ui/card";20import { StackedBar } from "@/components/ui/charts";21import { CountBarList } from "./bars";22import { CountryFlag } from "@/components/ui/country-flag";23import { Metric, MetricCell, MetricRow } from "@/components/ui/metric";24import { EmptyState, ErrorState, OfflineNotice } from "@/components/ui/states";25import { RowLink, TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table";26import { api } from "@/lib/api";27import type { DetailFacilitiesMeta } from "@/lib/dto";28import { fmtCompact, fmtDate, fmtInt, fmtMw, fmtPct, fmtUsd, pluralize, splitMw } from "@/lib/format";29import { OPERATOR_KIND_LABEL, TYPE_LABEL } from "@/lib/labels";30import { routes } from "@/lib/routes";31import { compactJsonLd, NOT_FOUND_METADATA, pageMetadata } from "@/lib/seo";32import type { SearchParams } from "@/lib/sort";33import { GrowthChart } from "./growth-chart";3435export const revalidate = 300;3637type Props = { params: Promise<{ slug: string }>; searchParams: Promise<SearchParams> };3839const HUGE = new Set(["US", "CA", "RU", "CN", "AU", "BR"]);40const SMALL = new Set(["SG", "HK", "LU", "MT", "BH", "QA", "AE", "IL", "NL", "BE", "CH", "IE", "DK", "TW", "KW", "CY", "EE", "LV", "LT", "SI", "PR", "IS"]);41// ≥ 5 so the API returns grid clusters instead of one country-level bubble42const zoomFor = (iso2: string) => (HUGE.has(iso2) ? 5 : SMALL.has(iso2) ? 7 : 5.5);4344function describe(c: CountryDetail): string {45  const bits = [`${fmtInt(c.facilityCount)} data center ${pluralize(c.facilityCount, "facility", "facilities")}`];46  if (c.knownMw) bits.push(`${fmtMw(c.knownMw)} known operational capacity (${fmtPct(c.mwCoverage)} of facilities publish a figure)`);47  const pipe = (c.constructionMw ?? 0) + (c.plannedMw ?? 0);48  if (pipe) bits.push(`≈ ${fmtMw(pipe)} in the pipeline`);49  bits.push(`${fmtInt(c.operatorCount)} ${pluralize(c.operatorCount, "operator")}`);50  bits.push(`${fmtInt(c.cloudRegionCount)} cloud ${pluralize(c.cloudRegionCount, "region")}`);51  if (c.hyperscaleCount) bits.push(`${fmtInt(c.hyperscaleCount)} hyperscale`);52  return `Data centers in ${c.name}: ${bits.join(", ")}. Markets, operators, project pipeline, AI infrastructure, cloud footprint, internet exchanges, energy context and grid constraints — every figure sourced.`;53}5455export async function generateMetadata({ params }: Props): Promise<Metadata> {56  const { slug } = await params;57  const r = await api.country(slug);58  if (!r.ok) return r.status === 404 ? NOT_FOUND_METADATA : { title: "Country", alternates: { canonical: routes.country(slug) } };59  return pageMetadata({ title: `Data Centers in ${r.data.name}`, description: describe(r.data), path: routes.country(r.data.slug) });60}6162function Mw({ mw, estimate }: { mw: number | null | undefined; estimate?: boolean }) {63  const s = splitMw(mw);64  if (!s) return <span className="text-ink-4">—</span>;65  return (66    <>67      {estimate && <EstimateMark className="mr-0.5 text-[0.6em]" />}68      {s.value}69    </>70  );71}72const unitOf = (mw: number | null | undefined) => splitMw(mw)?.unit ?? undefined;7374const IXP_ROWS = 15;7576export default async function CountryPage({ params, searchParams }: Props) {77  const [{ slug }, sp] = await Promise.all([params, searchParams]);78  const fPage = parseFPage(sp.fPage);79  const r = await api.country(slug, fPage > 1 ? { fPage } : undefined);80  if (!r.ok && r.status === 404) notFound();81  if (!r.ok) {82    return (83      <Page className="py-6">84        <OfflineNotice detail={r.error} className="mb-4" />85        <ErrorState />86      </Page>87    );88  }89  const c = r.data;90  const meta = (r.meta ?? {}) as DetailFacilitiesMeta;91  const facTotal = meta.facilitiesTotal ?? c.facilityCount;92  const facPer = meta.facilitiesPerPage ?? 50;93  const base = routes.country(c.slug);94  const statusTotal = Object.values(c.statusBreakdown).reduce((a, b) => a + (b ?? 0), 0);95  const types = (Object.entries(c.typeBreakdown) as Array<[FacilityType, number]>).filter(([, n]) => n > 0).sort((a, b) => b[1] - a[1]);96  const metros = [...c.metros].sort((a, b) => b.facilityCount - a.facilityCount);97  const topOps = [...c.topOperators].sort((a, b) => b.facilityCount - a.facilityCount).slice(0, 10);98  const cloud = [...c.cloudRegions].sort((a, b) => a.provider.name.localeCompare(b.provider.name) || a.code.localeCompare(b.code));99  const ixps = [...(c.ixps ?? [])].sort((a, b) => b.facilityCount - a.facilityCount || a.name.localeCompare(b.name));100  const ixpCount = c.ixpCount ?? ixps.length;101  const aiFacilities = c.aiFacilities ?? [];102  const aiProjects = c.aiProjects ?? [];103  const gridConstraints = c.gridConstraints ?? [];104  const claims = c.claims ?? [];105  const hasAi = aiFacilities.length > 0 || aiProjects.length > 0;106  const facTotalForShare = c.facilityCount || 1;107108  const jsonLd = compactJsonLd({109    "@context": "https://schema.org",110    "@type": "Country",111    name: c.name,112    identifier: c.iso2,113    url: `${routes.country(c.slug)}`,114    description: describe(c),115    geo: c.lat !== null && c.lng !== null ? { "@type": "GeoCoordinates", latitude: c.lat, longitude: c.lng } : undefined,116  });117118  const sections = [119    { id: "map", label: "Map", count: c.facilityCount },120    { id: "growth", label: "Growth" },121    { id: "breakdown", label: "Breakdown", count: statusTotal },122    { id: "markets", label: "Markets", count: metros.length },123    { id: "operators", label: "Operators", count: topOps.length },124    { id: "pipeline", label: "Pipeline" },125    { id: "ai", label: "AI", count: hasAi ? aiFacilities.length + aiProjects.length : 0 },126    { id: "cloud", label: "Cloud", count: cloud.length },127    { id: "connectivity", label: "Connectivity" },128    { id: "projects", label: "Projects", count: c.recentProjects.length },129    { id: "changes", label: "Changes", count: c.recentEvents.length },130    { id: "energy", label: "Energy" },131    { id: "grid", label: "Grid" },132    { id: "coverage", label: "Coverage" },133    { id: "facilities", label: "Facilities", count: facTotal },134    { id: "claims", label: "Claims", count: claims.length },135  ];136137  return (138    <Page>139      <JsonLd data={jsonLd} />140      <div className="pt-3">141        <Breadcrumbs items={[{ name: "Countries", href: routes.countries() }, { name: c.name, href: base }]} />142      </div>143144      <header className="py-5 sm:py-7">145        <div className="flex flex-wrap items-start justify-between gap-3">146          <div className="min-w-0">147            <div className="label-caps">Country · {c.iso2}</div>148            <h1 className="mt-1.5 flex items-center gap-2.5 text-[24px] font-semibold leading-tight tracking-[-0.02em] text-ink sm:text-[30px]">149              <CountryFlag iso2={c.iso2} size={26} />150              <span>Data centers in {c.name}</span>151            </h1>152            <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[13px] text-ink-2">153              {(c.region || c.subregion) && <span>{[c.region, c.subregion].filter((v, i, a): v is string => Boolean(v) && a.indexOf(v) === i).join(" · ")}</span>}154              {c.population !== null && (155                <span className="figure text-ink-3" title="Population, latest World Bank vintage">156                  {fmtCompact(c.population)} residents157                </span>158              )}159              {c.gdpUsd !== null && (160                <span className="figure text-ink-3" title="GDP, current US$, World Bank">161                  {fmtUsd(c.gdpUsd)} GDP162                </span>163              )}164              {(c.population !== null || c.gdpUsd !== null) && <span className="text-[11px] text-ink-4">World Bank</span>}165            </div>166          </div>167          <div className="flex flex-wrap items-center gap-2">168            <WatchButton entityType="country" slug={c.slug} name={c.name} />169            <PillLink href={routes.explore(`entity=facilities&country=${c.iso2}`)}>Explore →</PillLink>170          </div>171        </div>172      </header>173174      <MetricRow cols={5}>175        <MetricCell>176          <Metric value={fmtInt(c.facilityCount)} label="Facilities" note={`${fmtInt(c.operationalCount)} operational`} />177        </MetricCell>178        <MetricCell>179          <Metric value={<Mw mw={c.knownMw} />} unit={unitOf(c.knownMw)} label="Operational MW" note={<CoverageBar value={c.mwCoverage} label="MW coverage" width="w-full max-w-[150px]" />} />180        </MetricCell>181        <MetricCell>182          <Metric value={<Mw mw={c.constructionMw} estimate />} unit={unitOf(c.constructionMw)} label="Under construction" note={`${fmtInt(c.constructionCount)} ${pluralize(c.constructionCount, "site")}${c.constructionMw === null && c.constructionCount ? ", MW not published" : ""}`} />183        </MetricCell>184        <MetricCell>185          <Metric value={<Mw mw={c.plannedMw} estimate />} unit={unitOf(c.plannedMw)} label="Planned MW" note={`${fmtInt(c.plannedCount)} planned ${pluralize(c.plannedCount, "site")}${c.plannedMw === null && c.plannedCount ? ", MW not published" : ""}`} />186        </MetricCell>187        <MetricCell>188          <Metric value={c.announcedInvestmentUsd ? fmtUsd(c.announcedInvestmentUsd) : "—"} label="Announced investment" note={c.announcedInvestmentUsd ? "site-scoped project figures, published" : "not published"} />189        </MetricCell>190      </MetricRow>191      <MetricRow cols={5} className="border-t-0 [&>div]:border-t-0">192        <MetricCell>193          <Metric size="sm" value={fmtInt(c.operatorCount)} label="Operators" />194        </MetricCell>195        <MetricCell>196          <Metric size="sm" value={fmtInt(c.cloudRegionCount)} label="Cloud regions" />197        </MetricCell>198        <MetricCell>199          <Metric size="sm" value={fmtInt(c.hyperscaleCount)} label="Hyperscale" />200        </MetricCell>201        <MetricCell>202          <Metric size="sm" value={fmtInt(c.aiCount)} label="AI facilities" note="confirmed + likely" />203        </MetricCell>204        <MetricCell>205          <Metric size="sm" value={fmtInt(ixpCount)} label="Internet exchanges" />206        </MetricCell>207      </MetricRow>208209      {c.rankings.length > 0 && (210        <div className="-mx-3 mt-3 flex items-center gap-1.5 overflow-x-auto px-3 no-scrollbar sm:mx-0 sm:flex-wrap sm:px-0" aria-label="Ranking positions">211          <span className="label-caps shrink-0">Rankings</span>212          {c.rankings.map((rk) => (213            <Link key={rk.key} href={routes.ranking(rk.key)} className="inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-sm border border-hair bg-surface px-2 text-[12px] text-ink hover:bg-surface-2 sm:h-7">214              <span className="figure font-medium">215                #{rk.rank}216                <span className="text-ink-4">/{rk.total}</span>217              </span>218              <span className="text-ink-2">{rk.label}</span>219            </Link>220          ))}221        </div>222      )}223224      <SectionNav items={sections} className="mt-4" />225226      <div className="flex flex-col gap-8 pt-6">227        <Section id="map" title="Map" description="Facilities in the country; hollow rings are city-level locations. Cloud regions are listed below." className="scroll-mt-28">228          <EntityMap center={c.lat !== null && c.lng !== null ? { lat: c.lat, lng: c.lng } : null} zoom={zoomFor(c.iso2)} filters={{ country: c.iso2 }} openLabel={`Open ${c.name} in map`} className="h-[240px] sm:h-[380px]" />229        </Section>230231        <Section id="growth" title="Growth" description="By opening year of indexed facilities. Facilities without a published opening date are not charted." className="scroll-mt-28">232          <GrowthChart data={c.growth} />233        </Section>234235        <Section id="breakdown" title="Breakdown" description="Status and facility type of indexed records." className="scroll-mt-28">236          <div className="grid gap-6 md:grid-cols-2">237            <div>238              <div className="label-caps mb-2">Status</div>239              {statusTotal ? <StackedBar data={c.statusBreakdown} height={12} /> : <EmptyState compact title="No status data" />}240            </div>241            <div>242              <div className="label-caps mb-2">Type</div>243              {types.length ? <CountBarList items={types.map(([t, n]) => ({ label: TYPE_LABEL[t], value: n, href: routes.datacenters(`country=${c.iso2}&type=${t}`) }))} showRank={false} /> : <EmptyState compact title="No type data" />}244            </div>245          </div>246        </Section>247248        {metros.length > 0 && (249          <Section id="markets" title="Top markets" description={`${fmtInt(metros.length)} ${pluralize(metros.length, "metro")} with indexed infrastructure`} className="scroll-mt-28" aside={<Link href={routes.markets()} className="text-[12.5px] text-ink-3 hover:text-ink">All markets →</Link>}>250            <Table dense mobileCollapse={{ total: metros.length, label: "markets" }}>251              <THead>252                <tr>253                  <Th>Market</Th>254                  <Th numeric>Facilities</Th>255                  <Th numeric>Known MW</Th>256                  <Th numeric>Pipeline</Th>257                  <Th numeric className="hidden sm:table-cell">258                    Cloud regions259                  </Th>260                  <Th numeric className="hidden md:table-cell">261                    IXPs262                  </Th>263                  <Th numeric className="hidden md:table-cell">264                    Operators265                  </Th>266                </tr>267              </THead>268              <TBody>269                {metros.map((m) => {270                  const pipe = m.constructionMw === null && m.plannedMw === null ? null : (m.constructionMw ?? 0) + (m.plannedMw ?? 0);271                  return (272                    <Tr key={m.id}>273                      <Td primary>274                        <RowLink href={routes.market(m.slug)}>{m.name}</RowLink>275                        {m.regionName && m.regionName !== m.name && <span className="ml-1.5 text-[11.5px] text-ink-3">{m.regionName}</span>}276                      </Td>277                      <Td label="Facilities" numeric lead>278                        {fmtInt(m.facilityCount)}279                      </Td>280                      <Td label="Known MW" numeric>281                        {m.knownMw === null ? <span className="text-ink-4">—</span> : fmtMw(m.knownMw)}282                      </Td>283                      <Td label="Pipeline" numeric>284                        {pipe === null ? (285                          <span className="text-ink-4">—</span>286                        ) : (287                          <>288                            <EstimateMark className="mr-0.5" />289                            {fmtMw(pipe)}290                          </>291                        )}292                      </Td>293                      <Td label="Cloud regions" numeric className="hidden sm:table-cell">294                        {fmtInt(m.cloudRegionCount)}295                      </Td>296                      <Td label="IXPs" numeric className="hidden md:table-cell">297                        {fmtInt(m.ixpCount)}298                      </Td>299                      <Td label="Operators" numeric className="hidden md:table-cell">300                        {fmtInt(m.operatorCount)}301                      </Td>302                    </Tr>303                  );304                })}305              </TBody>306            </Table>307          </Section>308        )}309310        {topOps.length > 0 && (311          <Section id="operators" title="Top operators" description={`${fmtInt(c.operatorCount)} ${pluralize(c.operatorCount, "operator")} with facilities in ${c.name}; share of the country's facility count`} className="scroll-mt-28" aside={<Link href={routes.datacenters(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All facilities →</Link>}>312            <ShareList items={topOps.map((o) => ({ key: o.id, label: <>{o.name}{o.kind && <span className="ml-1.5 text-[11px] text-ink-3">{OPERATOR_KIND_LABEL[o.kind]}</span>}</>, href: routes.operator(o.slug), count: o.facilityCount, share: o.facilityCount / facTotalForShare, mw: o.knownMw ?? null }))} unit="facilities" />313          </Section>314        )}315316        <Section id="pipeline" title="Project pipeline" description="Facilities by lifecycle bucket with the MW they publish, then project records by stage." className="scroll-mt-28" aside={<Link href={routes.projects(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All projects →</Link>}>317          {c.pipeline ? <PipelineBars pipeline={c.pipeline} /> : <HonestEmpty title="Pipeline breakdown unavailable" />}318          <PublishedOnlyNote className="mt-3" />319        </Section>320321        {hasAi && (322          <Section id="ai" title="AI infrastructure" description="Facilities and projects with confirmed or likely AI / HPC evidence. A single keyword never confirms a site." className="scroll-mt-28" aside={<Link href={routes.ai()} className="text-[12.5px] text-ink-3 hover:text-ink">AI infrastructure index →</Link>}>323            {aiFacilities.length > 0 && (324              <div className="mb-4">325                <div className="label-caps mb-2">Facilities ({fmtInt(aiFacilities.length)})</div>326                <FacilityTable items={aiFacilities} columns={["operator", "market", "status", "mw", "type"]} />327              </div>328            )}329            {aiProjects.length > 0 && (330              <div>331                <div className="label-caps mb-2">Projects ({fmtInt(aiProjects.length)})</div>332                <ProjectList items={aiProjects} />333              </div>334            )}335          </Section>336        )}337338        {cloud.length > 0 && (339          <Section id="cloud" title="Cloud footprint" description={`${fmtInt(cloud.length)} public cloud ${pluralize(cloud.length, "region")} located in ${c.name} — associated with a market, not a specific facility unless a source says so`} className="scroll-mt-28" aside={<Link href={`${routes.cloudRegions()}?country=${c.iso2}`} className="text-[12.5px] text-ink-3 hover:text-ink">All cloud regions →</Link>}>340            <Table dense mobileCollapse={{ total: cloud.length, label: "cloud regions" }}>341              <THead>342                <tr>343                  <Th>Provider</Th>344                  <Th>Code</Th>345                  <Th className="hidden sm:table-cell">Name</Th>346                  <Th className="hidden md:table-cell">City</Th>347                  <Th numeric>AZs</Th>348                  <Th>Status</Th>349                  <Th className="hidden lg:table-cell">Launched</Th>350                </tr>351              </THead>352              <TBody>353                {cloud.map((cr) => (354                  <Tr key={cr.id}>355                    <Td primary>356                      <Link href={routes.operator(cr.provider.slug)} className="font-medium text-ink hover:text-accent hover:underline">357                        {cr.provider.name}358                      </Link>359                      <span className="ml-1.5 text-[11.5px] text-ink-3 sm:hidden">{cr.name}</span>360                    </Td>361                    <Td label="Code">362                      <RowLink href={routes.cloudRegion(cr.slug)} className="figure text-[12.5px] font-normal">363                        {cr.code}364                      </RowLink>365                    </Td>366                    <Td label="Name" className="hidden sm:table-cell">367                      {cr.name}368                    </Td>369                    <Td label="City" className="hidden md:table-cell" muted>370                      {cr.city ?? "—"}371                    </Td>372                    <Td label="AZs" numeric>373                      {cr.availabilityZones === null ? <span className="text-ink-4">—</span> : fmtInt(cr.availabilityZones)}374                    </Td>375                    <Td label="Status">376                      <span className="inline-flex flex-wrap gap-1">377                        <Badge tone={cr.status === "operational" ? "ok" : cr.status === "announced" ? "info" : cr.status === "retired" ? "neutral" : "warn"} size="xs">378                          {cr.status}379                        </Badge>380                        {cr.isSovereign && (381                          <Badge tone="outline" size="xs" title="Sovereign cloud region">382                            Sovereign383                          </Badge>384                        )}385                      </span>386                    </Td>387                    <Td label="Launched" className="figure hidden text-[12px] text-ink-3 lg:table-cell">388                      {cr.launchedOn ? fmtDate(cr.launchedOn) : "—"}389                    </Td>390                  </Tr>391                ))}392              </TBody>393            </Table>394          </Section>395        )}396397        <Section id="connectivity" title="Connectivity" description={`Internet exchanges with a published presence in ${c.name}'s facilities (PeeringDB and exchange operators).`} className="scroll-mt-28" aside={<Link href={`${routes.ixps()}?country=${c.iso2}`} className="text-[12.5px] text-ink-3 hover:text-ink">All {fmtInt(ixpCount)} IXPs →</Link>}>398          {ixps.length ? (399            <>400              <Table dense mobileCollapse={{ total: Math.min(ixps.length, IXP_ROWS), label: "exchanges" }}>401                <THead>402                  <tr>403                    <Th>Exchange</Th>404                    <Th className="hidden sm:table-cell">City</Th>405                    <Th numeric>Networks</Th>406                    <Th numeric>Facilities</Th>407                  </tr>408                </THead>409                <TBody>410                  {ixps.slice(0, IXP_ROWS).map((x) => (411                    <Tr key={x.id}>412                      <Td primary>413                        <RowLink href={routes.ixp(x.slug)}>{x.name}</RowLink>414                        {x.nameLong && x.nameLong !== x.name && <span className="ml-1.5 hidden text-[11.5px] text-ink-3 md:inline">{x.nameLong}</span>}415                      </Td>416                      <Td label="City" className="hidden sm:table-cell" muted>417                        {x.city ?? (x.metro?.name ?? "—")}418                      </Td>419                      <Td label="Networks" numeric>420                        {x.networkCount === null ? <span className="text-ink-4" title="Not published">—</span> : fmtInt(x.networkCount)}421                      </Td>422                      <Td label="Facilities" numeric lead>423                        {fmtInt(x.facilityCount)}424                      </Td>425                    </Tr>426                  ))}427                </TBody>428              </Table>429              {ixps.length > IXP_ROWS && (430                <p className="mt-2 text-[11.5px] text-ink-4">431                  First {IXP_ROWS} of {fmtInt(ixps.length)} exchanges by facility presence —{" "}432                  <Link href={`${routes.ixps()}?country=${c.iso2}`} className="underline decoration-hair-2 hover:text-ink">433                    see all434                  </Link>435                  .436                </p>437              )}438            </>439          ) : (440            <HonestEmpty title="No internet exchange presence recorded" detail="IXP presence comes from published IXP ↔ facility links; none is inferred" />441          )}442          <p className="mt-2 text-[11px] text-ink-4">443            Network counts are shown only when the exchange publishes them.{" "}444            <Link href={routes.connectivity()} className="underline decoration-hair-2 hover:text-ink">445              Connectivity index446            </Link>447          </p>448        </Section>449450        {c.recentProjects.length > 0 && (451          <Section id="projects" title="Recent projects" description="Announced and under-construction developments." className="scroll-mt-28" aside={<Link href={routes.projects(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All projects →</Link>}>452            <ProjectList items={c.recentProjects} />453          </Section>454        )}455456        {c.recentEvents.length > 0 && (457          <Section id="changes" title="Recent changes" description="Latest detections for facilities and projects in the country." className="scroll-mt-28" aside={<Link href={routes.live(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">Live feed →</Link>}>458            <EventList events={c.recentEvents.slice(0, 10)} dense />459          </Section>460        )}461462        <Section id="energy" title="Power & energy context" description="National electricity indicators for reading capacity figures against the grid." className="scroll-mt-28" aside={<Link href={routes.power()} className="text-[12.5px] text-ink-3 hover:text-ink">Power & grid →</Link>}>463          {c.energy ? <EnergyContextPanel e={c.energy} knownMw={c.knownMw} /> : <HonestEmpty title="No national electricity indicators recorded" />}464        </Section>465466        <Section id="grid" title="Grid constraints" description="Moratoria, grid delays, load caps, new transmission, regulation and large-load queues reported for this country — observed events and curated public reports only, nothing inferred." className="scroll-mt-28">467          <GridConstraintList items={gridConstraints} emptyDetail="no report or grid event has been indexed for this country; that is an absence of reports, not proof of an unconstrained grid" />468        </Section>469470        <Section id="coverage" title="Coverage" description="How much of this country the index actually knows — shown so the figures above can be read honestly." className="scroll-mt-28" aside={<Link href={routes.coverage()} className="text-[12.5px] text-ink-3 hover:text-ink">Coverage report →</Link>}>471          {c.coverage ? <CoverageStrip row={c.coverage} /> : <HonestEmpty title="Coverage not computed" />}472        </Section>473474        <Section id="facilities" title="Facilities" description={`${fmtInt(facTotal)} indexed · sorted by known capacity`} className="scroll-mt-28" aside={<Link href={routes.datacenters(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">Filter & export →</Link>}>475          <PagedFacilities items={c.facilities} total={facTotal} page={fPage} perPage={facPer} hrefFor={(p) => fPageHref(base, sp, p)} columns={["operator", "market", "status", "mw", "type", "confidence"]} />476        </Section>477478        {claims.length > 0 && (479          <Section id="claims" title="Claims about this country" description="Country-scoped figures (national programmes, aggregate capacity statements) — shown as evidence, never summed into facility totals." className="scroll-mt-28">480            <ClaimsTable claims={claims} kind="all" />481          </Section>482        )}483      </div>484485      <SourcesFooter sources={r.sources} note="Country totals are lower bounds computed from published or filed values only; coverage shows the share of facilities with a capacity figure. Population, GDP and electricity: World Bank (CC BY 4.0)." />486    </Page>487  );488}489